package sm.upgrade.api.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.HibernateTemplate;

import sm.codec.type.NullValue;
import sm.dam.api.DBDictService;
import sm.dbdict.DBDictChangePolicy;
import sm.dbdict.DBDictChangeUtil;
import sm.dbdict.DBDictUtil;
import sm.dbdict.change.DBDictChange;
import sm.dbdict.def.DBDict;
import sm.dbdict.def.Field;
import sm.dbdict.def.Key;
import sm.def.KeyType;
import sm.def.Type;
import sm.hibernate.DAMUtil;
import sm.upgrade.api.DBDictUpgradeService;
import sm.upgrade.api.DataService;
import sm.upgrade.api.KeyValidator;
import sm.upgrade.api.UpgradePhase;
import sm.upgrade.exception.UpgradeException;
import sm.upgrade.util.UpgradeResult;
import sm.upgrade.util.UpgradeUtil;
import sm.util.SMMap;
import sm.util.Util;

import com.google.gson.Gson;

public class DBDictUpgradeServiceImpl implements DBDictUpgradeService {

	private static Logger logger = LoggerFactory
			.getLogger(DBDictUpgradeService.class);

	private HibernateTemplate template;

	private DBDictService dbdictService;

	private KeyValidator newKeyValidator;

	private DBDictChangePolicy dbdictChangePolicy;

	private KeyValidator oldKeyValidator = new OldKeyValidator();

	public void setDbdictChangePolicy(DBDictChangePolicy dbdictChangePolicy) {
		this.dbdictChangePolicy = dbdictChangePolicy;
	}

	public void setTemplate(HibernateTemplate template) {
		this.template = template;
		newKeyValidator = new NewKeyValidator(template);
	}

	public void setDbdictService(DBDictService dbdictService) {
		this.dbdictService = dbdictService;
	}

	private void dropConflictKey(DBDict upgrade, DBDict customer,
			DBDict newDBDict, DataService upgradeDataService)
			throws UpgradeException {
		String fileName = newDBDict.getName();
		List<Key> oldKeys = DBDictChangeUtil.getNewKeys(upgrade, customer);
		if (oldKeys != null && oldKeys.size() > 0) {
			String entityName = Util.radEntityNameToJava(fileName);
			String[] joinedArrayPaths = DAMUtil.getJoinedArrayPaths(upgrade);
			List<SMMap> datas = upgradeDataService.getNextBatchDatas(
					entityName, 0, 0, joinedArrayPaths);
			for (Key oldKey : oldKeys) {
				boolean hasConflict = oldKeyValidator.validateKey(upgrade,
						oldKey, datas, true);

				if (hasConflict) {
					Key newKey = DBDictChangeUtil.getSameKey(
							newDBDict.getKey(), oldKey);
					DBDictUtil.removeKey(newDBDict, newKey);
					logger.warn("The customer "
							+ KeyType.getType(newKey.getFlags()).getName()
							+ " key " + DBDictUtil.getKeyString(newKey)
							+ " has conflict with upgrade data, drop it!");
				}
			}
		}
	}

	@Override
	public void upgradeDBDict(String fileName, DataService upgradeDataService,
			boolean fix) throws UpgradeException {
		DBDict upgrade = upgradeDataService.getDBDict(fileName);
		if (upgrade != null) {
			DBDict customer = dbdictService.getDBDict(fileName);
			Object[] keyValues = new Object[] { fileName };
			SMMap upgradeResult = null;
			if (customer == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Adding dbdict, " + fileName);
				}
				dbdictService.saveDBDict(upgrade);
				upgradeResult = UpgradeUtil.createUpgradeResult(
						DBDictService.DBDICT, keyValues, UpgradeResult.Added);

			} else {
				DBDict newDBDict = UpgradeUtil.upgradeDBDict(customer, upgrade);

				// drop conflict keys
				if (fix) {
					dropConflictKey(upgrade, customer, newDBDict,
							upgradeDataService);
				}

				// upgrade dbdict
				if (logger.isInfoEnabled()) {
					logger.info("Processing dbdict, " + fileName);
				}

				dbdictService.updateDBDict(newDBDict);
				upgradeResult = UpgradeUtil.createUpgradeResult(
						DBDictService.DBDICT, keyValues, UpgradeResult.Merged);

			}

			if (!UpgradePhase.UPGRADE_RESULTS.equals(fileName)) {
				template.save(UpgradePhase.UPGRADE_RESULTS, upgradeResult);
			}
		} else {
			throw new UpgradeException("The file name " + fileName
					+ " is not found.");
		}

	}

	@Override
	public boolean checkDBDictChange(String fileName,
			DataService upgradeDataService) throws UpgradeException {
		boolean hasChange = false;
		DBDict upgrade = upgradeDataService.getDBDict(fileName);
		if (upgrade != null) {
			DBDict customer = dbdictService.getDBDict(fileName);
			if (customer != null) {
				DBDict newDBDict = UpgradeUtil.upgradeDBDict(customer, upgrade);
				hasChange = DBDictChangeUtil.checkDBDictChange(customer,
						newDBDict, new DBDictChange(), dbdictChangePolicy);
			}
		}
		return hasChange;
	}

	private boolean isAutoTransformAble(int curType, int expectedType) {
		boolean canTransfer = false;
		if (curType == Type.NUMBER_TYPE && expectedType == Type.CHARACTER_TYPE) {
			canTransfer = true;
		}
		return canTransfer;
	}

	private boolean checkType(String fieldPath, Object value, int expectedType,
			String fileName, String[] keyPaths, SMMap dataMap) {
		boolean hasConflict = false;
		if (value != null && !(value instanceof NullValue)) {
			int realType = DAMUtil.getTypeFromValue(value);
			if (expectedType != realType) {
				hasConflict = true;
				Object[] keyValue = DAMUtil.getValues(dataMap, keyPaths);
				String keyMsg = "RC conflict for dbdict:" + fileName
						+ ",field:" + fieldPath + ",key:"
						+ Util.formatKey(keyValue);
				Gson gson = new Gson();
				String valueMsg = "value:" + gson.toJson(value)
						+ ", type changes from "
						+ Type.getType(realType).getName() + " to "
						+ Type.getType(expectedType).getName();
				if (isAutoTransformAble(realType, expectedType)) {
					logger.debug(keyMsg);
					logger.debug(valueMsg);
				} else {
					logger.info(keyMsg);
					logger.info(valueMsg);
				}
			}
		}
		return hasConflict;
	}

	@Override
	public boolean validateData(String fileName, DataService upgradeDataService)
			throws UpgradeException {
		boolean hasConflict = false;
		DBDict upgrade = upgradeDataService.getDBDict(fileName);
		DBDict customer = dbdictService.getDBDict(fileName);
		if (upgrade != null && customer != null) {
			Key identifer = DBDictUtil.getIdentifier(customer);
			// get rc field changes which may cause data loss
			List<Field> rcFieldChanges = DBDictChangeUtil.getRCFieldChange(
					customer, upgrade);
			if (rcFieldChanges.size() > 0) {
				List<Field> identiferFields = DBDictUtil.getKeyFields(
						identifer, customer.getField());
				String[] keyPaths = DBDictUtil.getKeyPaths(identiferFields);
				keyPaths = Util.radFieldPathsToJava(keyPaths);
				List<Object[]> checkValues = createCheckValues(rcFieldChanges);
				String entityName = Util.radEntityNameToJava(fileName);
				List<Object> datas = template.find("from " + entityName);
				for (Object data : datas) {
					SMMap dataMap = (SMMap) data;
					for (Object[] checkValue : checkValues) {
						String arrayPath = (String) checkValue[0];
						String arrayFieldPath = (String) checkValue[1];
						int expectedType = (Integer) checkValue[2];
						if (arrayPath != null) {
							List<Object> arrayFields = (List<Object>) dataMap
									.get(arrayPath);
							if (arrayFields != null) {
								for (int i = 0; i < arrayFields.size(); i++) {
									Object arrayField = arrayFields.get(i);
									Map<String, Object> arrayFieldMap = (Map<String, Object>) arrayField;
									Object fieldValue = arrayFieldMap
											.get(arrayFieldPath);
									boolean fieldHasConflict = checkType(
											arrayPath + "[" + i + "]."
													+ arrayFieldPath,
											fieldValue, expectedType, fileName,
											keyPaths, dataMap);
									if (!hasConflict) {
										hasConflict = fieldHasConflict;
									}
								}
							}
						} else {
							Object fieldValue = dataMap.get(arrayFieldPath);
							boolean fieldHasConflict = checkType(
									arrayFieldPath, fieldValue, expectedType,
									fileName, keyPaths, dataMap);
							if (!hasConflict) {
								hasConflict = fieldHasConflict;
							}
						}

					}

				}
			}
		}
		return hasConflict;
	}

	private List<Object[]> createCheckValues(List<Field> rcFieldChanges) {
		List<Object[]> checkValues = new ArrayList<Object[]>();
		for (Field field : rcFieldChanges) {
			Field arrayParent = DBDictUtil.getArrayParent(field);
			String arrayPath = null;
			String fieldPath = null;
			if (arrayParent != null) {
				arrayPath = DBDictUtil.getFieldPath(arrayParent);
				fieldPath = DBDictUtil.getArrayFieldPath(field, arrayParent);
			} else {
				fieldPath = DBDictUtil.getFieldPath(field);
			}

			if (arrayPath != null) {
				arrayPath = Util.radFieldPathToJava(arrayPath);
			}
			fieldPath = Util.radFieldPathToJava(fieldPath);
			int type = field.getType();
			checkValues.add(new Object[] { arrayPath, fieldPath, type });
		}
		return checkValues;
	}

	@Override
	public boolean validateNewKey(String fileName, boolean fix,
			DataService upgradeDataService) throws UpgradeException {
		boolean hasConflict = false;
		DBDict upgrade = upgradeDataService.getDBDict(fileName);
		DBDict customer = dbdictService.getDBDict(fileName);
		if (upgrade != null && customer != null) {
			List<Key> newKeys = DBDictChangeUtil.getNewKeys(customer, upgrade);
			if (newKeys != null && !newKeys.isEmpty()) {
				String entityName = Util.radEntityNameToJava(fileName);
				List<SMMap> datas = template.find("from " + entityName);
				for (Key newKey : newKeys) {
					newKeyValidator.validateKey(customer, newKey, datas, fix);
				}
				return hasConflict;
			}
		}

		return hasConflict;
	}

	@Override
	public boolean validateOldKey(String fileName,
			DataService upgradeDataService) throws UpgradeException {
		boolean hasConflict = false;
		DBDict upgrade = upgradeDataService.getDBDict(fileName);
		DBDict customer = dbdictService.getDBDict(fileName);
		if (upgrade != null && customer != null) {
			List<Key> oldKeys = DBDictChangeUtil.getNewKeys(upgrade, customer);
			if (oldKeys != null && !oldKeys.isEmpty()) {
				String entityName = Util.radEntityNameToJava(fileName);
				String[] joinedArrayPaths = DAMUtil
						.getJoinedArrayPaths(upgrade);
				List<SMMap> datas = upgradeDataService.getNextBatchDatas(
						entityName, 0, 0, joinedArrayPaths);
				for (Key oldKey : oldKeys) {
					oldKeyValidator.validateKey(upgrade, oldKey, datas, false);
				}
			}
		}

		return hasConflict;
	}
}
